﻿using System;
using System.Linq;
using FluentValidation;
using LINQPad.Extensibility.DataContext;
using LINQPad.OpenAccess.Data;
using LINQPad.OpenAccess.Helpers;
using LINQPad.OpenAccess.Services;
using LINQPad.OpenAccess.Settings;
using LINQPad.OpenAccess.UI.Services;

namespace LINQPad.OpenAccess.UI.ViewModel
{
    /// <summary>
    ///   <see cref="ConnectionSettingsViewModel" /> for using a pre-defined connection.
    /// </summary>
    public class PredefinedConnectionSettingsViewModel : ConnectionSettingsViewModel<PredefinedConnectionSettingsViewModel>
    {
        /// <summary>
        /// The default description.
        /// </summary>
        public const string DefaultDescription = "uses a predefined connection from an app.config";
        /// <summary>
        /// The default name.
        /// </summary>
        public const string DefaultName = "predefined";
        #region Private

        #region - Vars

        private readonly IApplicationConfigService _appConfigService;
        private PredefinedConnectionData _predefinedConnection;
        private PredefinedConnectionData[] _predefinedConnections;

        #endregion

        #region - Functions

        /// <summary>
        /// Loads the predefined connections.
        /// </summary>
        private void LoadPredefinedConnections( )
        {
            PredefinedConnectionData[] predefinedConnections = null;
            var owner = this.Owner;
            if ( owner != null && owner.BasicSettings.AppConfigFilePathExists )
                this._appConfigService.TryGetPredefinedConnections( owner.BasicSettings.AppConfigFilePath, out predefinedConnections );
            this.PredefinedConnections = predefinedConnections;
        }

        #endregion

        #endregion

        #region Public

        #region - Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="PredefinedConnectionSettingsViewModel" /> class.
        /// </summary>
        /// <param name="appConfigService">The application config service.</param>
        /// <param name="validator">The validator.</param>
        public PredefinedConnectionSettingsViewModel( IApplicationConfigService appConfigService, IUserInteractionService userInteractionService, IValidator<PredefinedConnectionSettingsViewModel> validator )
            : base( DefaultName, DefaultDescription, userInteractionService, validator )
        {
            Guard.ArgumentNotNull( appConfigService, "appConfigService" );
            this._appConfigService = appConfigService;
            this._predefinedConnections = new PredefinedConnectionData[0];
        }

        #endregion

        #region - Properties

        /// <summary>
        /// Gets a value indicating whether this instance has at least one predefined connection.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance has at least one predefined connection; otherwise, <c>false</c>.
        /// </value>
        public bool HasPredefinedConnections
        {
            get
            {
                return this._predefinedConnections != null && this._predefinedConnections.Length > 0;
            }
        }
        /// <summary>
        /// Gets or sets the predefined connection.
        /// </summary>
        /// <value>
        /// The predefined connection.
        /// </value>
        public PredefinedConnectionData PredefinedConnection
        {
            get
            {
                return this._predefinedConnection;
            }
            set
            {
                if ( this._predefinedConnection != value )
                {
                    this._predefinedConnection = value;
                    this.OnPropertyChanged( ( ) => this.PredefinedConnection );
                    this.Invalidate( );
                }
            }
        }
        /// <summary>
        /// Gets or sets the predefined connections.
        /// </summary>
        /// <value>
        /// The predefined connections.
        /// </value>
        public PredefinedConnectionData[] PredefinedConnections
        {
            get
            {
                return this._predefinedConnections;
            }
            set
            {
                if ( this._predefinedConnections != value )
                {
                    this._predefinedConnections = value ?? new PredefinedConnectionData[0];
                    this.OnPropertyChanged( ( ) => this.PredefinedConnections );
                    this.OnPropertyChanged( ( ) => this.HasPredefinedConnections );
                }
            }
        }

        #endregion

        #endregion

        #region ConnectionSettingsViewModel Members

        /// <summary>
        /// Determines whether the specified provider is supported.
        /// </summary>
        /// <param name="providerName">Name of the provider.</param>
        /// <returns>
        ///   <c>true</c> if the specified provider is supported; otherwise, <c>false</c>.
        /// </returns>
        public override bool IsProviderSupported( string providerName )
        {
            return true;
        }
        /// <summary>
        /// Refreshes this instance.
        /// </summary>
        public override void Refresh( )
        {
            this.LoadPredefinedConnections( );
        }
        /// <summary>
        /// Determines whether this instance is currently able to test the connection.
        /// </summary>
        /// <returns>
        ///   <c>true</c> if this instance is currently able to test the connection; otherwise, <c>false</c>.
        /// </returns>
        protected override bool CanTestConnection( )
        {
            return base.CanTestConnection( ) && this._predefinedConnection != null;
        }
        /// <summary>
        /// Called to actually load the view model's settings.
        /// </summary>
        protected override void DoLoadSettings( )
        {
            this.LoadPredefinedConnections( );
            var connectionSettings = this.DriverSettings.Connection as PredefinedConnectionSettings;
            this.PredefinedConnection = connectionSettings == null ? null : this._predefinedConnections.FirstOrDefault( e => e.Name == connectionSettings.ConnectionName );
        }
        /// <summary>
        /// Called to actually test the connection.
        /// <para>Should throw a corresponding exception if the test fails.</para>
        /// </summary>
        /// <param name="connectionInfo">The connection info.</param>
        /// <param name="driverSettings">The driver settings.</param>
        /// <exception cref="System.InvalidOperationException">No database information available !</exception>
        protected override void DoTestConnection( IConnectionInfo connectionInfo, DriverSettings driverSettings )
        {
            var predefinedConnection = this._predefinedConnection;
            if ( predefinedConnection == null || String.IsNullOrWhiteSpace( this._predefinedConnection.ConnectionString ) )
                throw new InvalidOperationException( "Predefined connection invalid or not set !" );

            var databaseInfo = connectionInfo.DatabaseInfo;
            if ( databaseInfo == null )
                throw new InvalidOperationException( "No database information available !" );

            var oldCustomCxString = databaseInfo.CustomCxString;
            var oldProvider = databaseInfo.Provider;
            try
            {
                databaseInfo.CustomCxString = predefinedConnection.ConnectionString;
                if ( !String.IsNullOrWhiteSpace( predefinedConnection.ProviderName ) )
                    databaseInfo.Provider = predefinedConnection.ProviderName;

                base.DoTestConnection( connectionInfo, driverSettings );
            }
            finally
            {
                databaseInfo.CustomCxString = oldCustomCxString;
                databaseInfo.Provider = oldProvider;
            }
        }
        /// <summary>
        /// Called to actually write the view model's settings.
        /// </summary>
        protected override void DoWriteSettings( )
        {
            var predefinedConnection = this._predefinedConnection;
            this.DriverSettings.Connection = new PredefinedConnectionSettings { ConnectionName = predefinedConnection == null ? null : predefinedConnection.Name };
        }
        #endregion
    }
}
